Skip to content

Fix transient detection for proxy #2045

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 18, 2019
Merged

Conversation

bahusoid
Copy link
Member

@bahusoid bahusoid commented Mar 8, 2019

Fixes #2043

@fredericDelaporte
Copy link
Member

The change suspected in #2043 dates back to 5.0. Is this a regression of 5.0?

@bahusoid
Copy link
Member Author

bahusoid commented Mar 8, 2019

Is this a regression of 5.0?

Yes. By adding call to method ForeignKeys.IsTransientFast -> IEntityPersister.IsTransient -> IEntityTuplizer.GetIdentifier which doesn't work with proxies.

So in fix I've added support for proxies (1st place as shortcut to ForeignKeys.IsTransientFast, and 2nd place to support proxies in AbstractEntityTuplizer.GetIdentifier)

@fredericDelaporte fredericDelaporte added this to the 5.2.5 milestone Mar 11, 2019
@fredericDelaporte
Copy link
Member

This one will have to be back-ported down to 5.0.

@@ -114,6 +114,11 @@ public object Instantiate(object id)

public object GetIdentifier(object entity)
{
if (entity is INHibernateProxy proxy)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places the caller checks for proxy before calling this method.

Copy link
Member

@fredericDelaporte fredericDelaporte Mar 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, in NHibernate.Type.EntityType, the checks done there are no more needed with this PR changes.

There are other cases where the proxy is unproxied before hand, but that looks to me as a requirement of the implemented features there rather than due to GetIdentifier not supporting proxies.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places the caller checks for proxy before calling this method.

And your point? You prefer to keep it that way? To me it looks better to support it here.
Or at least move this check to AbstractEntityPersister.GetIdentifier.

If it's about clean up other places - I think we can't remove checks in other places in this PR to avoid some custom implementations relying on it. I can add 6.0 TODO here for clean up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to keep this check outside of this method as in other places.

Copy link
Member Author

@bahusoid bahusoid Mar 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how about moving it to AbstractEntityPersister.GetIdentifier? As I don't see why persister.GetIdentifier shouldn't handle proxies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am seeing more benefits in having the checks inside that method rather than outside. I do not really see why this method should not do the check itself. It does look very lightweight to me, so having the check done for cases where it can never be a proxy should not have a significant impact. What am I missing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredericDelaporte I agree with you. But if you are OK with my latest changes I think this part is out of scope of this PR as now IsTransient logic never calls GetIdentifier for proxies

@bahusoid

This comment has been minimized.

{
return Task.FromResult<bool?>(false);
}
if (session.PersistenceContext.IsEntryFor(entity))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually as shows NH607 this check is wrong. PersistenceContext can contain entries for transient objects.
And hack was added to support wrong implementation of IsNotTransientSlow:

/***********************************************/
// TODO NH verify the behavior of NH607 test
// these lines are only to pass test NH607 during PersistenceContext porting
// i'm not secure that NH607 is a test for a right behavior
EntityEntry entry = session.PersistenceContext.GetEntry(entity);
if (entry != null)
return entry.Id;

So in this PR I'm going to move this check back to IsNotTransientSlow and in new PR for 5.3 will try to remove it completly.

@@ -171,6 +171,9 @@ public static bool IsNotTransientSlow(string entityName, object entity, ISession
/// </remarks>
public static bool? IsTransientFast(string entityName, object entity, ISessionImplementor session)
{
if (entity.IsProxy())
return false;
Copy link
Member

@fredericDelaporte fredericDelaporte Mar 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, despite some hesitations on that subject, now proxies are considered as never transient.

What happens if we load a proxy of an entity with an assigned identifier, initialize it, delete it in session, flush, and save it again? In such case we would have a transient proxy, and it should be possible to save it, I guess. If that case does work with currently released NHibernate, then the logic from "not transient" should not be applied to "transient" too. (And we should consider removing it from the "not" case.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scenario still works (both direct and cascace saving) - as entity is unproxied along the way to IsTransientFast. I will commit tests for such cases later.

But from correctness standpoint you are right - my initial implementation is more correct. It correctly detects transient objects from any context. So I think I simply need to drop all my commits to the state you've approved initially. Do you agree? That means that GetIdentifier fix is also required here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your initial implementation has my preference.

@hazzik,

I would prefer to keep this check outside of this method as in other places.

Is-it because this is a patch of 5.2.x? So you consider that this kind of (even small) "semantic" change should be avoided?
(I do not actually see it as a semantic change. I see it more like fixing a bug in GetIdentifier, since its documentation does not state it does not support proxies.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I did not have time to clarify my position on this subject. While this check is ok here, I think it should be performed outside of this method. First of all, AbstractEntityPersister/AbstractEntityTuplizer does not deal with any kind of instantiaded proxies currently (except a delegated method to instantiate one). Secondly, ISessionImplementor.GetEntityPersister(...) is a very heavy method and really should be left as a last resort. So, check for the proxy should happen outside of the persister to avoid its lookup.

Copy link
Member Author

@bahusoid bahusoid Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISessionImplementor.GetEntityPersister(...) is a very heavy method and really should be left as a last resort.

Why single dictionary lookup is considered heavy? And in our case it's persister.IsTransient method that calls GetIdentifier. So we really save nothing.
It's either put this check in persister.IsTransient vs GetIdentifier (persister or tupilizer). To me the deeper the better - to cover all possible future cases (so in tupilizer).

AbstractEntityPersister/AbstractEntityTuplizer does not deal with any kind of instantiaded proxies currently

I take it you meant - non-instantiated. As I already tell EntityPersister must be able to work with such proxy. For now it's only for IsTransient method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why single dictionary lookup is considered heavy?

You are confusing it with another method that is of ISessionFactory

Copy link
Member Author

@bahusoid bahusoid Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. It could be heavy for subclasses. But that wasn't really the point. My main point for proper IsTransient detection we need to support proxies right inside persister. And it's better to do right in GetIdentifier method

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed changes to the point I think is the right fix. Feel free to adjust it your way

@fredericDelaporte fredericDelaporte self-requested a review March 16, 2019 19:42
Copy link
Member

@fredericDelaporte fredericDelaporte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this last way of handling proxies? It avoids having the persister dealing with proxies for now. We could still go back on that subject in 5.3, while doing this minimal fix for 5.2.x.

@fredericDelaporte fredericDelaporte merged commit 656a88b into nhibernate:5.2.x Mar 18, 2019
fredericDelaporte pushed a commit to fredericDelaporte/nhibernate-core that referenced this pull request Mar 18, 2019
fredericDelaporte pushed a commit to fredericDelaporte/nhibernate-core that referenced this pull request Mar 19, 2019
fredericDelaporte pushed a commit that referenced this pull request Mar 20, 2019
fredericDelaporte pushed a commit that referenced this pull request Mar 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants